package com.ruoyi.erp.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.lock.LockInfo;
import com.baomidou.lock.exception.LockException;
import com.baomidou.lock.executor.RedissonLockExecutor;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.google.common.collect.Sets;
import com.ruoyi.common.core.domain.PageQuery;
import com.ruoyi.common.core.mybatisplus.core.ServicePlusImpl;
import com.ruoyi.common.core.page.TableDataInfo;
import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.common.utils.BeanCopyUtils;
import com.ruoyi.erp.domain.SysCodeRule;
import com.ruoyi.erp.domain.SysCodeRuleDetail;
import com.ruoyi.erp.domain.bo.SysBusinessBo;
import com.ruoyi.erp.domain.bo.SysCodeRuleBo;
import com.ruoyi.erp.domain.enun.RuleType;
import com.ruoyi.erp.domain.vo.SysBusinessVo;
import com.ruoyi.erp.domain.vo.SysCodeRuleVo;
import com.ruoyi.erp.mapper.SysCodeRuleMapper;
import com.ruoyi.erp.service.ISysBizSerialService;
import com.ruoyi.erp.service.ISysBusinessService;
import com.ruoyi.erp.service.ISysCodeRuleDetailService;
import com.ruoyi.erp.service.ISysCodeRuleService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import javax.validation.Valid;
import javax.validation.constraints.NotBlank;
import java.util.Collection;
import java.util.Date;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

/**
 * 编码规则Service业务层处理
 *
 * @author chenyun
 * @date 2022-03-06
 */
@Slf4j
@Service
public class SysCodeRuleServiceImpl extends ServicePlusImpl<SysCodeRuleMapper, SysCodeRule, SysCodeRuleVo> implements ISysCodeRuleService {
    private static final String UNIQUE_KEY = "rule_code";
    private static final String LOCK_KEY = "SysCodeRule:rule_code";

    @Resource
    private ISysCodeRuleDetailService sysCodeRuleDetailService;
    @Resource
    private ISysBizSerialService sysBizSerialService;
    @Override
    public SysCodeRuleVo queryById(Long id){
        SysCodeRuleVo re = getVoById(id);
        QueryWrapper<SysCodeRuleDetail> query = new QueryWrapper();
        query.eq("code_rule_id",id);
        query.orderBy(true , true, "order_num");
        List<SysCodeRuleDetail> list = sysCodeRuleDetailService.list(query);
        re.setSysCodeRuleDetailList(list);
        return re;
    }

    @Override
    public TableDataInfo<SysCodeRuleVo> queryPageList(SysCodeRuleBo bo, PageQuery pageQuery) {
        LambdaQueryWrapper<SysCodeRule> lqw = buildQueryWrapper(bo);
        Page<SysCodeRuleVo> result = pageVo(pageQuery.build(), lqw);
        return TableDataInfo.build(result);
    }

    @Override
    public List<SysCodeRuleVo> queryList(SysCodeRuleBo bo) {
        LambdaQueryWrapper<SysCodeRule> lqw = buildQueryWrapper(bo);
        return listVo(lqw);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Boolean insertByBo(SysCodeRuleBo bo) throws Exception{
        SysCodeRule add = BeanUtil.toBean(bo, SysCodeRule.class);
        validEntityBeforeSave(add);
        QueryWrapper wrapper = existQueryWrapper(add);
        boolean flag = insertIdempotency(add,LOCK_KEY,wrapper,getMsg(add));
        if (flag) {
            bo.setId(add.getId());
        }
        if (CollUtil.isNotEmpty(bo.getSysCodeRuleDetailList())) {
            List<SysCodeRuleDetail> list = bo.getSysCodeRuleDetailList().stream().map(u -> {
                SysCodeRuleDetail crd = new SysCodeRuleDetail();
                BeanCopyUtils.copy(u , crd);
                crd.setId(add.getId());
                crd.setOrgId(add.getOrgId());
                return crd;
            }).collect(Collectors.toList());
            sysCodeRuleDetailService.saveAll(list);
        }
        return flag;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Boolean updateByBo(SysCodeRuleBo bo) throws Exception{
        SysCodeRule update = BeanUtil.toBean(bo, SysCodeRule.class);
        validEntityBeforeSave(update);
        QueryWrapper wrapper = existQueryWrapper(update);
        boolean flag = updateIdempotency(update,LOCK_KEY,wrapper,getMsg(update));
        if (CollUtil.isNotEmpty(bo.getSysCodeRuleDetailList())) {
            List<SysCodeRuleDetail> list = bo.getSysCodeRuleDetailList().stream().map(u -> {
                SysCodeRuleDetail crd = new SysCodeRuleDetail();
                BeanCopyUtils.copy(u , crd);
                crd.setCodeRuleId(bo.getId());
                crd.setOrgId(bo.getOrgId());
                return crd;
            }).collect(Collectors.toList());
            QueryWrapper<SysCodeRuleDetail> query = new QueryWrapper();
            query.eq("code_rule_id", bo.getId());
            Set<Long> oldSet = sysCodeRuleDetailService.list(query).stream()
                .map(SysCodeRuleDetail::getId).collect(Collectors.toSet());
            Set<Long> newSet = list.stream().filter( u -> u.getId() != null).map(SysCodeRuleDetail::getId).collect(Collectors.toSet());
            // 判断要删除的数据
            Set<Long> delSet = CollectionUtil.newHashSet();
            Sets.difference(oldSet, newSet).copyInto(delSet);
            sysCodeRuleDetailService.removeByIds(delSet);
            sysCodeRuleDetailService.saveOrUpdateAll(list);
        }
        return flag;
    }

    /**
     * 保存前的数据校验
     *
     * @param entity 实体类数据
     */
    private void validEntityBeforeSave(SysCodeRule entity){
        //TODO 做一些数据校验,如唯一约束
    }

    @Override
    public Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid) {
        if(isValid){
            //TODO 做一些业务上的校验,判断是否需要校验
        }
        return removeByIds(ids);
    }
    @Resource
    private ISysBusinessService sysBusinessService;

    @Override
    public List<String> genCodeByRule(@Valid @NotBlank(message="业务编码不能为空") String bizCode, Date date, Integer count) {
        SysBusinessBo bo = new SysBusinessBo();
        bo.setBizCode(bizCode);
        List<SysBusinessVo> listBiz = sysBusinessService.queryList(bo);
        if (CollUtil.isEmpty(listBiz)){
            throw new ServiceException("没有该业务："+bizCode);
        } else if (listBiz.size() >1){
            throw new ServiceException(bizCode+"存在多项业务");
        }
        SysBusinessVo bus = listBiz.get(0);
        Long busId = bus.getId();

        SysCodeRuleVo rule = queryById(busId);

        if (rule == null){
            throw new ServiceException("编码规则不存在");
        }
        List<SysCodeRuleDetail> listDetail = rule.getSysCodeRuleDetailList();
        if (CollUtil.isEmpty(listDetail)){
            throw new ServiceException("编码规则明细不存在");
        }

        // 分布式锁 获取 流水号
        if (lockTemplate == null) {
            throw new LockException("lockTemplate is null");
        }
        Integer year = null;
        Integer period = null;
        if (date != null) {
            // TODO 根据时间查期间
        }
        String lockKey = UNIQUE_KEY + bizCode;
        if (year != null) {
            lockKey = lockKey+year;
        }
        if (period != null) {
            lockKey = lockKey+period;
        }
        final LockInfo lockInfo = lockTemplate.lock(lockKey, 30000L, 5000L, RedissonLockExecutor.class);
        if (null == lockInfo) {
            throw new RuntimeException("业务处理中,请稍后再试");
        }
        // 获取锁成功，处理业务
        count = count==null || count< 1 ? 1 : count;
        List<String> result = CollUtil.newArrayList();
        try {
            // 生成多个编号，首先获取单独的固定字符串，流水号(当前第一个流水号)遍历
            List<String> strList = CollUtil.newArrayList();
            int serialIndex = -1;

            for (int i = 0 ; i<listDetail.size(); i++){
                SysCodeRuleDetail de = listDetail.get(i);
                String code = getCode(busId, de, year, period, count);
                strList.add(code);
                if (RuleType.SERIAL.getValue().equals(de.getType())){
                    if(StrUtil.isEmpty(code)){
                        log.error("流水号获取失败：bizCode{}、date{}、count{}",bizCode,date,count);
                        throw new ServiceException("获取流水号失败");
                    }
                    serialIndex = i;
                }
            }

            if (count>1){
                // 持久化来源流水号
                if (serialIndex == -1){
                    throw new ServiceException("不存在流水号，无法生成多个编码");
                } else {
                    // 流水号生成规则
                    SysCodeRuleDetail de = listDetail.get(serialIndex);
                    String serial = strList.get(serialIndex);
                    long num = Long.valueOf(serial);
                    StringBuilder sbPrefix = new StringBuilder();
                    for (int i=0 ; i<serialIndex ; i++){
                        sbPrefix.append(strList.get(i));
                    }
                    String prefixStr = sbPrefix.toString();

                    StringBuilder sbSuffix = new StringBuilder();
                    if (serialIndex <strList.size()-1){
                        for (int i=serialIndex+1 ; i<strList.size() ; i++){
                            sbSuffix.append(strList.get(i));
                        }
                    }
                    Integer length = de.getLength();
                    String suffixStr = sbSuffix.toString();
                    long maxNum = num+count;
                    for (long i=num; i<=maxNum; i++) {
                        StringBuilder sbCode = new StringBuilder();
                        sbCode.append(prefixStr);
                        if (length==null||length<1){
                            sbCode.append(i);
                        }else{
                            String code = String.format("%"+0+length+"d", i);
                            sbCode.append(code);
                        }
                        sbCode.append(suffixStr);
                        result.add(sbCode.toString());
                    }
                }
            }else {
                StringBuilder sb = new StringBuilder();
                strList.stream().forEach(u -> sb.append(u));
                result.add(sb.toString());
            }
        } finally {
            //释放锁
            lockTemplate.releaseLock(lockInfo);
        }
        return result;
    }


    private String getCode(Long busId,SysCodeRuleDetail de,Integer year ,Integer period ,Integer count){
        String type = de.getType();
        String value = de.getValue();

        Integer length = de.getLength();

        String fillChar = de.getFillChar()==null?"0":de.getFillChar();

        switch(type){
            case "str":
                return value;
            case "time":
                return DateUtil.format(DateUtil.date() , value);
            case "serial":
                Long initNum = StrUtil.isNumeric(de.getValue())?Long.valueOf(de.getValue()) : 1L;
                Long num = sysBizSerialService.getSerialNum(de.getOrgId(),busId.toString() ,initNum,year,period,count);
                return String.format("%"+fillChar+length+"d", num);
            default:
                return "";
        }
    }


    /**
     * 唯一判断查询条件，暂支持一个键
     * @param entity
     * @return
     */
    private QueryWrapper<SysCodeRule> existQueryWrapper(SysCodeRule entity) {
        return new QueryWrapper<SysCodeRule>().eq(UNIQUE_KEY, entity.getRuleCode());
    }

    private String getMsg(SysCodeRule entity){
            String unique = String.valueOf(entity.getRuleCode());
            return "编码["+unique+"]已存在";
    }
}
